/**
 * Copyright © 2013 Creativor Studio.
 * About license information, please see LICENSE.txt.
 */
package org.creativor.rayson.server.transport.nio;

import java.io.IOException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.concurrent.atomic.AtomicLong;

import org.creativor.rayson.server.RpcServer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * An NIO listener of {@link RpcServer}.
 * 
 * @author creativor
 */
public class NioListener extends Thread {
	private static AtomicLong UID = new AtomicLong(0);
	private static Logger LOGGER = LoggerFactory.getLogger(NioListener.class);
	private final Selector selector;
	private boolean running = true;
	private final RpcServer server;

	/**
	 * Instantiates a new listener.
	 * 
	 * @param server
	 *            the server
	 * @throws IOException
	 *             Signals that an I/O exception has occurred.
	 */
	public NioListener(final RpcServer server,
			final ServerSocketChannel socketChannel) throws IOException {
		setName("Server " + server.toString() + " lisenter");
		this.server = server;
		this.selector = Selector.open();
		socketChannel.register(selector, SelectionKey.OP_ACCEPT);
	}

	/**
	 * Accept new connection.
	 * 
	 * @param key
	 */
	private void doAccept(final SelectionKey key) {
		final ServerSocketChannel channel = (ServerSocketChannel) key.channel();
		// NioConnection connection = null;

		try {
			final SocketChannel clientChannel = channel.accept();
			clientChannel.configureBlocking(false);
			// register read events for the new connection.
			final SelectionKey clientKey = clientChannel.register(selector,
					SelectionKey.OP_READ);
			final ServerNioConnection connection = new ServerNioConnection(
					clientChannel, clientKey);
			clientKey.attach(connection);
			// try
			// {
			// this.server.getConnectionManager().acceptPending(connection);
			// } catch (DenyServiceException e)
			// {
			// connection.denyToAccept();
			// }

			// if (server.getConfig().debug())
			LOGGER.info("new connection: " + connection + " accepted.");

		} catch (final IOException e) {
			LOGGER.error("Accept new connection error", e);
			return;
		}

	}

	/**
	 * Handling read event in server socket channel.
	 * 
	 * @param key
	 */
	private void doRead(final SelectionKey key) {
		final ServerNioConnection connection = (ServerNioConnection) key
				.attachment();
		int readCount = -1;
		try {
			readCount = connection.asyncRead();
		} catch (final IOException e) {
			// Ignore this exception.
		}
		if (readCount == -1) {
			try {
				connection.close();
			} catch (final IOException e) {
				// Do nothing about this exception.
			}
			// this.server.getConnectionManager().remove(connection);
			// if (server.getConfig().debug())
			// LOGGER.info(connection.toString() + " removed!");
		}

	}

	/**
	 * Handling write event in server socket channel.
	 * 
	 * @param key
	 */
	private void doWrite(final SelectionKey key) {
		final ServerNioConnection connection = (ServerNioConnection) key
				.attachment();
		try {
			connection.asyncWrite();
		} catch (final IOException e) {
			try {
				connection.close();
			} catch (final IOException e1) {
				// Do nothing about this exception.
			}
			// this.server.getConnectionManager().remove(connection);
			// if (server.getConfig().debug())
			// LOGGER.info(connection.toString() + " removed!");
		}
	}

	/**
	 * Quit this listener thread.
	 */
	public void quit() {
		this.running = false;
	}

	/**
	 * Run.
	 */
	@Override
	public void run() {
		LOGGER.info(getName() + " starting...");

		SelectionKey key;

		Iterator<SelectionKey> iterator;

		while (running) {

			try {

				selector.select();

				for (iterator = selector.selectedKeys().iterator(); iterator
						.hasNext();) {
					key = iterator.next();
					iterator.remove();
					if (key.isValid()) {
						if (key.isAcceptable())
							doAccept(key);
						else if (key.isReadable())
							doRead(key);
						else if (key.isWritable())
							doWrite(key);
						else {
							LOGGER.error("Listener got un-excepted key: "
									+ key.toString());
						}
					}
				}
			} catch (final Throwable e) {
				// protect this thread not to quit.
				LOGGER.error("Server listener got error", e);
			}

		}
		LOGGER.info(getName() + " stopped");

	}
}
